/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.caf.data.jpa.repository.support;

import io.iec.caf.data.jpa.repository.CafI18nColumnDict;
import io.iec.caf.data.jpa.repository.CafI18nEntity;
import io.iec.caf.data.jpa.repository.CafJpaRepository;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryEvent;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryEventArgs;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryPublish;
import io.iec.caf.data.jpa.utils.TemporaryHibernateIdentifier;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.data.multilang.CAFMultiLanguageColumn;
import io.iec.edp.caf.data.source.DataSourceTypeRecognizer;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.hibernate.jpa.TypedParameterValue;
import org.hibernate.metamodel.internal.MetamodelImpl;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.PersistersContext;
import org.hibernate.type.StandardBasicTypes;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;

/**
 * This is {@link CafJpaRepositoryImpl}.
 *
 * @author yisiqi
 * @since 1.0.0
 */
@Slf4j
@Transactional(readOnly = true)
class CafJpaRepositoryImpl<E, ID extends Serializable> extends SimpleJpaRepository<E, Serializable> implements CafJpaRepository<E, Serializable> {


    private final JpaEntityInformation<E, ID> entityInformation;
    private final EntityManager entityManager;
    private final MetamodelImpl metamodel;

    private final List<Field> i18nColumns = new ArrayList<>();


    public CafJpaRepositoryImpl(JpaEntityInformation<E, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.entityManager = entityManager;
        if (!(entityManager.getMetamodel() instanceof MetamodelImpl)) {
            throw new UnsupportedOperationException();
        }
        this.metamodel = (MetamodelImpl) entityManager.getMetamodel();

        if (TemporaryHibernateIdentifier.NATIVE) {
            resolveI18nColumns(entityInformation.getJavaType());
        }
    }


    /*@Override
    @Transactional
    public <T extends E> T saveI18nEntity(T entity, List<CafI18nPropertyValue> values) {
        // EntityPersister persister = metamodel.entityPersister(entity.getClass());
        // AbstractEntityPersister abstractEntityPersister = (AbstractEntityPersister) persister;
        // abstractEntityPersister.processInsertGeneratedProperties();
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }*/

    @Override
    @Transactional
    public <T extends E> T save(T entity, CafI18nColumnDict dict) {
        T persistedEntity = this.save(entity);
        ID id = entityInformation.getId(entity);
        if (dict != null && !dict.getAllColumnsDict().isEmpty()) {
            AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.entityPersister(entity.getClass().getCanonicalName());
            persister.getRootTableName();

            StringBuilder builder = new StringBuilder("update ")
                    .append(persister.getRootTableName()).append(" ")
                    .append("set ");
            // update clause
            Map<String, String> params = dict.getAllColumnsDict();
            int paramCount = params.size();
            int count = 0;
            for (Map.Entry<String, String> entry : params.entrySet()) {
                count++;
                if (count == paramCount) {
                    builder.append(" ").append(entry.getKey()).append(" = :").append(entry.getKey());
                } else {
                    builder.append(" ").append(entry.getKey()).append(" = :").append(entry.getKey()).append(",");
                }

            }
            // where clause
            String[] idNames = persister.getIdentifierColumnNames();
            builder.append(" where ").append(idNames[0]).append(" = :id");
            // bind parameters value
            Query query = entityManager.createNativeQuery(builder.toString(), entity.getClass());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                //只有sql server数据库单独处理
                if(DataSourceTypeRecognizer.getSystemDbType().isImplicitConvert()){
                    query.setParameter(entry.getKey(), new TypedParameterValue(StandardBasicTypes.NSTRING, entry.getValue()));
                }else{
                    query.setParameter(entry.getKey(), entry.getValue());
                }

            }
            // bind id value
            query.setParameter("id", id);
            query.executeUpdate();

            // refresh entity
            this.entityManager.refresh(persistedEntity);
        }

        var jpaRepoEvent = SpringBeanUtils.getBean(CafJpaRepositoryPublish.class);
        if (jpaRepoEvent != null) {
            CafJpaRepositoryEventArgs args = new CafJpaRepositoryEventArgs(Objects.requireNonNull(dict).getAllColumnsDict(), entity, dict);
            CafJpaRepositoryEvent event = new CafJpaRepositoryEvent(args);
            jpaRepoEvent.onHandleCafJpaRepoSave(event);
        }


        return persistedEntity;
    }


    @Override
    public Optional<CafI18nEntity<E>> findByIdWithAllI18nColumn(Serializable id) {
        Optional<E> persistedEntity = findById(id);

        if (!TemporaryHibernateIdentifier.NATIVE) {
            Class<E> entityClass = entityInformation.getJavaType();
            AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.entityPersister(entityClass);

            List<String> columns = PersistersContext.getI18nEntityColumnsMap().get(entityClass.getCanonicalName());
            Map<String,String> langs = CafJpaLanguageThreadHolder.getLanguages();

            if (persistedEntity.isPresent() && (CollectionUtils.isEmpty(columns) || CollectionUtils.isEmpty(langs))) {
                log.warn(CollectionUtils.isEmpty(columns) ? "Can not find any i18n columns." : "Can not find any languages.");
                return Optional.of(CafI18nEntity.<E>builder().entity(persistedEntity.get()).build());
            } else if (!persistedEntity.isPresent()) {
                return Optional.empty();
            }

            StringBuilder sqlBuilder = new StringBuilder().append("select ");
            String[] idNames = persister.getIdentifierColumnNames();
            columns.forEach((c) -> langs.entrySet().forEach((l) ->
                    sqlBuilder.append(c).append("_").append(l.getValue().replaceAll("^_", "")).append(", ")));
            sqlBuilder
                    .deleteCharAt(sqlBuilder.length() - 2)
                    .append("from ").append(persister.getRootTableName())
                    .append(" where ").append(idNames[0]).append(" = :id");
            List<Tuple> tuples = entityManager
                    .createNativeQuery(sqlBuilder.toString(), Tuple.class)
                    .setParameter("id", id)
                    .getResultList();
            Object[] values = tuples.get(0).toArray();

            CafI18nColumnDict dict = new CafI18nColumnDict();
            for (int i = 0; i < columns.size(); i++) {
                String col = columns.get(i);
                int j = 0;
                for (Map.Entry<String, String> entry : langs.entrySet()) {
                    var value = values[i * langs.size() + j];
                    if (value != null)
                        dict.put(col, entry.getKey(), String.valueOf(value));
                    j++;
                }
            }

            CafI18nEntity<E> entity = CafI18nEntity.<E>builder()
                    .entity(persistedEntity.get())
                    .dict(dict)
                    .build();
            return Optional.of(entity);
        } else {
            Map<String, String> langs = CafJpaLanguageThreadHolder.getLanguages();

            if (persistedEntity.isPresent() && (CollectionUtils.isEmpty(i18nColumns) || CollectionUtils.isEmpty(langs))) {
                log.warn(CollectionUtils.isEmpty(i18nColumns) ? "Can not find any i18n columns." : "Can not find any languages.");
                return Optional.of(CafI18nEntity.<E>builder().entity(persistedEntity.get()).build());
            } else if (!persistedEntity.isPresent()) {
                return Optional.empty();
            }

            E e = persistedEntity.get();
            CafI18nColumnDict dict = new CafI18nColumnDict();
            for (Field column : i18nColumns) {
                column.setAccessible(true);
                try {
                    Object cols = column.get(e);
                    if (cols instanceof CAFMultiLanguageColumn) {
                        CAFMultiLanguageColumn ele = (CAFMultiLanguageColumn) cols;
                        dict.put(column.getName(), ele.getValues());
                    }
                } catch (IllegalAccessException exception) {
                    log.error("Can not access field [{}] of entity [{}]", column.getName(), e.getClass(), exception);
                }
            }

            CafI18nEntity<E> entity = CafI18nEntity.<E>builder()
                    .entity(persistedEntity.get())
                    .dict(dict)
                    .build();
            return Optional.of(entity);
        }
    }

    private void resolveEmbeddedClass(Class<?> embeddedClazz) {
        resolveI18nColumns(embeddedClazz);
    }

    private void resolveSuperClass(Class<?> superClazz) {
        resolveI18nColumns(superClazz);
    }

    private void resolveI18nColumns(Class<?> clazz) {
        Class<?> superClazz = clazz.getSuperclass();
        if (superClazz != null && !(superClazz == Object.class)) {
            resolveSuperClass(superClazz);
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(Transient.class) != null) {
                continue;
            }
            if (field.getAnnotation(Embedded.class) != null) {
                if (field.getType() == CAFMultiLanguageColumn.class) {
                    i18nColumns.add(field);
                } else {
                    resolveEmbeddedClass(field.getType());
                }
            }
        }
    }

}
